﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jay.Sc2.Bof.Lib.Action;
using Jay.Sc2.Bof.Lib.State;
using Jay.Sc2.Bof.Lib.Action.Terran;
using Jay.Sc2.Bof.Lib;
using Jay.Sc2.Bof.Lib.Entity;
using Jay.Sc2.Bof.Lib.Tools;

namespace Jay.Sc2.Bof.Console
{
	class Program
	{

		static string StateSummary(IState state)
		{
			return string.Format("(M:{0} G:{1} S:{2}-{3}/{4})", (int)state.Minerals, (int)state.Gas, state.Food, state.Workers, state.Supply);
		}

		public class EntityStateComparer : IComparer<EntityState>
		{
			public static readonly EntityStateComparer Instance = new EntityStateComparer();
			public int MinBuildTime = int.MaxValue;
			public int BestTime = int.MaxValue;

			public int Compare(EntityState x, EntityState y)
			{
				if (x == y) return 0;
				if (MinBuildTime > x.BuildTimes.Count) MinBuildTime = x.BuildTimes.Count;
				if (MinBuildTime > y.BuildTimes.Count) MinBuildTime = y.BuildTimes.Count;
				int compare = 0;
				double estX = x.EstimatedTotalTime;
				double estY = y.EstimatedTotalTime;
				if (x.EstimatedTimeLeft <= 0.0 && y.EstimatedTimeLeft > 0.0) return -1;
				if (y.EstimatedTimeLeft <= 0.0 && x.EstimatedTimeLeft > 0.0) return 1;
				//if (estX < BestTime && estY >= BestTime) return -1;
				//if (estY < BestTime && estX >= BestTime) return 1;
				//if (compare == 0) compare = estX.CompareTo(estY);
				if (compare == 0) compare = x.EstimatedTotalTime.CompareTo(y.EstimatedTotalTime);
				if (compare == 0) compare = -(x.Minerals + y.Gas).CompareTo(y.Minerals + y.Gas);
				if (compare == 0) compare = -x.ResourcesPerGameSecond.CompareTo(y.ResourcesPerGameSecond);
				if (compare == 0) compare = -x.Supply.CompareTo(y.Supply);
				return compare;
			}
		}

		static void FillUsefullEntities(List<IEntity> usefullEntities, IEntity entity, bool withRequirements, bool withInputs, bool withProducers)
		{
			if (usefullEntities.Contains(entity)) return;
			usefullEntities.Add(entity);
			if(withRequirements)
				foreach (IEntity required in entity.Requirements)
					FillUsefullEntities(usefullEntities, required, withRequirements, withInputs, withProducers);
			if(withInputs)
				foreach (IDictionary<IEntity, int> inputDico in entity.Inputs)
					foreach (IEntity input in inputDico.Keys)
						FillUsefullEntities(usefullEntities, input, withRequirements, withInputs, withProducers);
			if(withProducers)
				foreach (IEntity producer in entity.Producers)
					FillUsefullEntities(usefullEntities, producer, withRequirements, withInputs, withProducers);
		}

		static int RemoveUseless(List<EntityState> done, int minBuildTimes)
		{
			int count = 0;
			for (int c = 0; c < done.Count; c++)
			{
				EntityState state = done[c];
				if (state.BuildTimes.Count < minBuildTimes)
				{
					count++;
					done.RemoveAt(c);
					c--;
				}
			}
			return count;
		}

		static void Main(string[] args)
		{
			EntityXmlLoader loader = EntityXmlLoader.Instance;
			List<IEntity> entities = loader.CreateEntityList("Resources/Entities.xml", "Terran");
			int buildings = 0;
			int units = 0;
			int upgrades = 0;
			int specials = 0;
			foreach (IEntity entity in entities)
			{
				switch (entity.Type)
				{
					case "Unit": units++; break;
					case "Building": buildings++; break;
					case "Upgrade": upgrades++; break;
					case "Special": specials++; break;
					default: throw new Exception("Unknow entity Type : '" + entity.Type + "'.");
				}
			}
			System.Console.WriteLine(string.Format("{0} units, {1} buildings, {2} upgrades, {3} specials.", units, buildings, upgrades, specials));

			EntityState goal = new EntityState();
			goal.Time = 1200;
			//goal[loader.Get("Marine")] = 10;
			//goal[loader.Get("Medivac")] = 2;
			//goal[loader.Get("Marauder")] = 4;
			//goal[loader.Get("Banshee")] = 2;
			//goal[loader.Get("Orbital Command")] = 2;
			//goal[loader.Get("SCV")] = 56;
			//goal[loader.Get("Refinery")] = 4;
			goal[loader.Get("Battlecruiser")] = 1;


			EntityState state = new EntityState();
			state[loader.Get("Command Center")] = 1;
			state[loader.Get("SCV")] = 6;
			state.Supply = 11;
			state.Food = 6;
			state.Minerals = 50;
			state.HarvesterOnMinerals = 6;
			state.Goal = goal;

			List<EntityState> possible = new List<EntityState>();
			List<EntityState> done = new List<EntityState>();
			state.UpdateIncomeRate();
			state.EstimatedTimeLeft = state.EstimateTimeLeft(state.Goal);
			possible.Add(state);

			List<IEntity> usefullEntities = new List<IEntity>();

			IEntity harvester = loader.Get("SCV");
			usefullEntities.Add(harvester);

			int lastMinBuildTimes = 0;

			foreach (IEntity entity in entities)
				if (goal.CountEntity(entity) > 0)
					FillUsefullEntities(usefullEntities, entity, true, true, true);

			EntityState bestState = null;
			DateTime lastWriteConsole = DateTime.Now.Subtract(new TimeSpan(0, 0, 1));
			while (possible.Count > 0)
			{
				EntityStateComparer.Instance.BestTime = (bestState == null ? goal.Time : Math.Min(goal.Time, bestState.Time));
				goal.Time = EntityStateComparer.Instance.BestTime;
				int best = 0;
				state = possible[best];
				possible.RemoveAt(best);
				if (state.Time > EntityStateComparer.Instance.BestTime)
					continue;
				done.Add(state);
				if (state.IsOver)
				{
					if (bestState == null || state.Time < bestState.Time) 
					{
						bestState = state;
						for(int c=0;c<done.Count;c++)
							if(done[c].Time >= bestState.Time)
							{
								done.RemoveAt(c);
								c--;
							}
						for (int c = 0; c < possible.Count; c++)
							if (possible[c].Time >= bestState.Time)
							{
								possible.RemoveAt(c);
								c--;
							}
					}
					continue;
				}
				int maxWaitTime = Math.Min(3 * 60, EntityStateComparer.Instance.BestTime - state.Time);
				List<EntityState> otherPossible = state.GeneratePossibleStates(usefullEntities, maxWaitTime);
				foreach (EntityState possibleState in otherPossible)
				{
					if (possibleState.EstimatedTimeLeft > 0.0 && possibleState.EstimatedTimeLeft < (EntityStateComparer.Instance.BestTime * 0.2) && possibleState.EstimatedTotalTime > (EntityStateComparer.Instance.BestTime * 1.2))
						continue;
					if (!done.Contains(possibleState))
					{
						int found = possible.IndexOf(possibleState);
						if (found == -1)
						{
							if (possibleState.Time < EntityStateComparer.Instance.BestTime)
								possible.Add(possibleState);
						}
						else
						{
							EntityState already = possible[found];
							if (possibleState.Time < already.Time)
							{
								possible.RemoveAt(found);
								possible.Add(possibleState);
							}
						}
					}
				}
				EntityStateComparer.Instance.MinBuildTime = int.MaxValue;
				possible.Sort(EntityStateComparer.Instance);
				int minBuildTimes = EntityStateComparer.Instance.MinBuildTime;
				if (lastMinBuildTimes < minBuildTimes)
				{
					int useless = RemoveUseless(done, minBuildTimes);
					lastMinBuildTimes = minBuildTimes;
				}
				if (DateTime.Now.Subtract(lastWriteConsole).CompareTo(new TimeSpan(0, 0, 1)) > 0)
				{
					lastWriteConsole = DateTime.Now;
					System.Console.WriteLine(string.Format("completion = {0} %, best score = {1}, left to do = {2}, time = {3}", (done.Count * 100.0 / (double)(possible.Count + done.Count)).ToString("0.00"), state.EstimatedTotalTime.ToString("0.00"), state.EstimatedTimeLeft.ToString("0.00"), state.Time));
				}
			}
			System.Console.WriteLine("==== Press any key to leave the program ====");
			System.Console.ReadKey(true);
		}
	}
}
